package sebastiankerckhof.render.raytrace;

import sebastiankerckhof.CgPanel;
import sebastiankerckhof.math.Color3f;
import sebastiankerckhof.math.Point3f;
import sebastiankerckhof.math.Vector3f;
import sebastiankerckhof.render.Config;
import sebastiankerckhof.render.Renderer;
import sebastiankerckhof.render.raytrace.Hit.Hit;
import sebastiankerckhof.scene.FieldOfView;
import sebastiankerckhof.scene.Scene;
import sebastiankerckhof.scene.lighting.Light;
import sebastiankerckhof.scene.texture.TexCoord2f;

//TODO: Optimize for threads
public class RayTracer implements Renderer{

	public static final float EPSILON = 1e-12f;
	public static final int DEPTH = 5;
	
	Scene scene;
	CgPanel panel;
	FieldOfView fov;
	
	@Override
  public void render(Scene scene, CgPanel panel, FieldOfView fov) {
		
		this.scene = scene;
		this.panel = panel;
		this.fov = fov;
		
		int aasamples = Config.aasamples;
		int motionsteps = Math.max(1, Config.motionsteps);
		float motionStepifier = 1.0f/motionsteps;
		float colormultiplier = 1.0f/(aasamples*aasamples*motionsteps);
	  for(int x = 0; x < fov.nx; x++){

	  	for(int y = 0; y < fov.ny; y++){
	  		//System.out.println("x: "+x+", y: "+y);
	  		//System.out.println(r);
	  		
	  		Color3f color = new Color3f();
	  		float random, xp, yp;
	  		Ray r;
	  		
	  		for(int p = 0; p < aasamples; p++){
	  			for(int q = 0; q < aasamples; q++){
	  				xp = x; yp =y;
	  				
	  				if(aasamples > 1){
	  					random = (float) Math.random();
	  					xp += (float) ((p + random) / aasamples);
	  					yp += (float) ((p + random) / aasamples);
	  				}
	  				
	  	  		for(int i = 0; i < motionsteps; i++){
	  	  			r = generateRay(xp, yp);
	  	  	
	  	  			float step = i*motionStepifier;
	  	  			if(Config.motionblur) scene.transformToStep(step);
	  	  			//for(int j = 0; j < 8; j++){
	  	  				//r.origin.add(new Point3f((float) Math.random() *0.5f,(float) Math.random() *0.5f,(float)Math.random() *0.5f));
	  	  				color = color.add(rayColor(r, 0, DEPTH));
	  	  			//}
	  	  			
	  	  			if(Config.motionblur) scene.transformFromStep(step);
	  	  		}

	  			}
	  		}
	  		
	  		color = color.multiply(colormultiplier);
	  		panel.drawPixel(x, y, color.x, color.y, color.z);
       }
	  }
  }
	
	protected Color3f rayColor(Ray r, float t0, int depth){
		
		Hit hit = Hit.NO_HIT.clone();
		
		if(scene.hit(r, fov.near, hit)){	//if we hit an object, calculate the color
			
			TexCoord2f t = hit.getTexCoord();
			Color3f tc = null;
			if(t != null) //if there is a texture, start out with texture color
				return hit.surface.getTexture().getColor(t);

				
				Point3f hitPoint = r.origin.plus(r.direction.scale(hit.distance));
				hitPoint = hitPoint.add(hit.getNormal());
				
				Color3f color = new Color3f();
  		
				for(Light l : scene.lights){
  				Vector3f source = l.getDirectionToLight(r, hitPoint);
 
  				if(!Config.shadows || !scene.hit(new Ray(hitPoint, source), EPSILON, Hit.NO_HIT.clone())){
  					color = color.add(hit.surface.getMaterial().getShade(tc, l.getColor(), source, hit.getNormal(), r));
  				}
  				
  		    // mirrored term
  				//var reflection = addVector(scale(normal, -2*dot(dir, normal)), dir);
          //Vector3f reflection = hit.getNormal().scale(-2 * hit.getNormal().dotProduct(r.direction)).add(r.direction);
          //Ray mirrorRay = new Ray(hitPoint, reflection);
          //color = color.add(rayColor(mirrorRay, 0.01f, depth - 1).scale(0.25f));
  			}
				
				return color; //TODO: what with a lot of lights?
       
		}else{	//if we did not hit an object, return background color
			return scene.getBackgroundColor(r);
		}
		
	}
	
	public Ray generateRay(float x, float y){
		Ray r = new Ray();
		
		float u = (float) (fov.left + ((fov.right - fov.left) * (x + 0.5f) * (1.0f / fov.nx)));
		float v = (float) (fov.bottom + ((fov.top - fov.bottom) * (y + 0.5f) * (1.0f / fov.ny)));

		r.direction = 
    new Vector3f()        				
		.plus(scene.camera.w.scale(fov.near))
            				.plus(scene.camera.u.scale(u))
            				.plus(scene.camera.v.scale(v));
		

		
		r.origin = (Point3f) scene.camera.eye.clone();
		return r;
	}

}
